using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Daemon;
using Daemon.Reflection;
using Daemon.Data.Infrastructure.FrameWork;
namespace Daemon.Data.Infrastructure.FrameWork
{
    public static class LinqAggregateExtensionMethods
    {
        /// <summary>
        /// Calculate aggregate for queryable.
        /// </summary>
        /// <param name="queryable">The queryable.</param>
        /// <param name="aggregateItem">The aggregate item.</param>
        /// <returns>Aggregated result.</returns>
        public static object Aggregate<TEntity>(this IQueryable<TEntity> queryable, AggregateItem aggregateItem, bool retrievePropertyInfo = false)
            where TEntity : class
        {
            string originalfiledName = aggregateItem.FieldName; // single fields in the DB may be split into two fields in Viewfinder,save the field name in viewfinder.
            var propertyMap = new PropertyMap(typeof(TEntity));
            PropertyInfo property = propertyMap.FindNestedProperty(aggregateItem.FieldName).First();

            var parameter = Expression.Parameter(typeof(TEntity), "o");

            Dictionary<string, PropertyInfo> sourceProperties = new Dictionary<string, PropertyInfo>();
            sourceProperties[aggregateItem.FieldName] = property;
            Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);

            IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(parameter, sourceProperties[p.Name]))).OfType<MemberBinding>();
            var newExpression = Expression.New(dynamicType);
            var memberInit = Expression.MemberInit(Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings);
            var lambda = Expression.Lambda<Func<TEntity, object>>(memberInit, parameter);

            IQueryable<object> queryable2 = queryable.Select(lambda).ToList().Select(x => dynamicType.GetField(aggregateItem.FieldName).GetValue(x)).AsQueryable();
            var decorateAggregateQuerable = DecorateAggregateQuerable(queryable2, property, aggregateItem.DataType);

            if (retrievePropertyInfo)
            {
                return new Tuple<object, PropertyInfo>(GetAggregateResult(decorateAggregateQuerable, aggregateItem), property);
            }

            return GetAggregateResult(decorateAggregateQuerable, aggregateItem);
        }

        /// <summary>
		/// Decorate Aggregate Querable.
		/// </summary>
		/// <param name="queryable">queryable to be decrated.</param>
		/// <param name="propertyInfo">propertyInfo to get type.</param>
		/// <param name="dataType">the data type of field.</param>
		/// <returns>decrated queryable.</returns>
		private static IEnumerable<dynamic> DecorateAggregateQuerable(this IEnumerable<dynamic> queryable, PropertyInfo propertyInfo, string dataType)
        {
            var isString = propertyInfo.PropertyType == typeof(string);
            queryable = queryable.Where(c => c != null);
            if (isString)
            {
                queryable = queryable.Select(x => x.ToString().Trim());
            }

            if (isString && queryable.Count() > 0)
            {
                queryable = queryable.Where(c => !string.IsNullOrEmpty((string)c));
            }

            if (string.Equals(dataType, "date", StringComparison.OrdinalIgnoreCase))
            {
                queryable = queryable.Select(x => DateTime.Parse(x.ToString("yyyy-MM-dd")));
            }

            return queryable;
        }

        private static object GetAggregateResult(IEnumerable<dynamic> queryable2, AggregateItem aggregateItem)
        {
            object result = null;
            int count = queryable2.Count();

            switch (aggregateItem.AggregateOperator)
            {
                case AggregateOperator.Count:
                    result = count;
                    break;
                case AggregateOperator.DistinctCount:
                    result = queryable2.Distinct().ToList().Count();
                    break;
                case AggregateOperator.Distinct100:
                    result = queryable2.Distinct().Take(100).ToList();
                    break;
                case AggregateOperator.Distinct:
                    result = queryable2.Distinct().ToList();
                    break;
                case AggregateOperator.Min:
                    result = queryable2.Min();
                    break;
                case AggregateOperator.Max:
                    result = queryable2.Max();
                    break;
                case AggregateOperator.Average:
                    if (count != 0)
                    {
                        result = queryable2.Where(r => r != null).ToList().Average(c => (double)BlueearthConvert.ChangeType(c, typeof(double)));
                    }
                    else
                    {
                        result = 0;
                    }

                    break;
                case AggregateOperator.Sum:
                    result = queryable2.Where(r => r != null).ToList().Sum(c => (double)BlueearthConvert.ChangeType(c, typeof(double)));
                    break;
                case AggregateOperator.Range:
                    if (count != 0)
                    {
                        result = new List<object>() { queryable2.Min(), queryable2.Max() };
                    }
                    else
                    {
                        result = new List<object>() { 0, 0 };
                    }

                    break;
            }

            return result;
        }
    }


}